//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.ManagementStudioControls.HealthCheck
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;

    public class ExecutionServiceHealthCheck : HealthCheckBase
    {
        /// <summary>
        /// Faulty machines table.
        /// </summary>
        private Hashtable faultyMachines;

        /// <summary>
        /// Gets or sets the heart beat timeout.
        /// </summary>
        /// <value>The heart beat timeout.</value>
        public int HeartbeatTimeout { get; set; }

        /// <summary>
        /// Occurs when [execution service health check event].
        /// </summary>
        public event ExecutionServiceHealthCheckEventHandler ExecutionServiceHealthCheckEvent;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExecutionServiceHealthCheck"/> class.
        /// </summary>
        /// <param name="heartbeatTimeout">The heart beat timeout.</param>
        public ExecutionServiceHealthCheck(int heartbeatTimeout)
        {
            this.faultyMachines = new Hashtable();
            this.HeartbeatTimeout = heartbeatTimeout;
        }

        /// <summary>
        /// Performs the health check.
        /// </summary>
        /// <param name="RegistryHandle"></param>
        public override void PerformHealthCheck(Connection registryHandle)
        {
            this.RegistryHandle = registryHandle;
            this.faultyMachines.Clear();

            try
            {
                GetFaultyNodes(MachineStatus.Faulty, SchedulerStatus.Faulty);
                OnHealthCheckEvent(faultyMachines);
            }
            catch (Exception exp)
            {
                TridentErrorHandler.HandleUIException(exp);
            }
        }

        /// <summary>
        /// Called when [execution service health check event].
        /// </summary>
        /// <param name="faultyNodes">The faulty nodes.</param>
        protected virtual void OnHealthCheckEvent(Hashtable data)
        {
            ExecutionServiceHealthCheckEvent(this, new ExecutionServiceHealthCheckEventArgs(data));
        }

        /// <summary>
        /// Gets the faulty nodes.
        /// </summary>
        /// <param name="machineStatus">The machine status.</param>
        public void GetFaultyNodes(MachineStatus machineStatus, SchedulerStatus schedulerStatus)
        {
            try
            {
                Machine.ISearch searchQuery = Machine.ISearch.Create();
                searchQuery.Query = Machine.ISearch.Type(EnumFieldBase.Condition.NotEquals, MachineType.Virtual);

                List<Machine> availableMachines = Machine.Search(searchQuery, this.RegistryHandle);

                var faultyExecuterMachines = from machine in availableMachines
                                             where (DateTime.UtcNow.AddSeconds(-this.HeartbeatTimeout) > machine.LastPollTime)
                                             select machine;

                foreach (var faultyMachine in faultyExecuterMachines)
                {
                    string statusMessage = ManagementStudioResourceManager.GetString("ServiceStatus");

                    // Update the machine status as faulty in registry.
                    if (MachineStatus.Faulty != faultyMachine.Status)
                    {
                        faultyMachine.Status = machineStatus;
                        faultyMachine.ExecutorStatusMessage = statusMessage + faultyMachine.Status;
                        faultyMachine.SchedulerStatus = schedulerStatus;
                        faultyMachine.SchedulerStatusMessage =
                            ManagementStudioResourceManager.GetString("ExecutionServiceNotWorkingMessage");
                        faultyMachine.Save();
                    }

                    this.faultyMachines.Add((faultyMachine as IObject).ID, faultyMachine);
                }

                var faultySchedulerMachines = from machine in availableMachines
                                              where (machine.SchedulerStatus == schedulerStatus)
                                              select machine;


                foreach (Machine machine in faultySchedulerMachines)
                {
                    if (!this.faultyMachines.ContainsKey((machine as IObject).ID))
                    {
                        this.faultyMachines.Add((machine as IObject).ID, machine);
                    }
                }
            }
            catch (RecordNotFoundException)
            {
                // No machines available.
            }
            catch (Exception)
            {
                TridentErrorHandler.ThrowTridentCustomException(
                    TridentErrorConstants.ErrorNumber1000000038,
                    ManagementStudioResourceManager.GetString("GetFaultyNodesError"));
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public delegate void ExecutionServiceHealthCheckEventHandler(object sender, HealthCheckEventArgs e);
}
